//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/backend/llvm/llvm_types.h"
#include "sagittarius/session/session.h"
#include "sagittarius/io/io.h"
#include <stdio.h>
#include <iostream>

// for utils
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_os_ostream.h"

namespace Sagittarius {
    
    LLVM_Types::LLVM_Types() {
        initPrimitives();
        initSGTypes1();
    }
    
    void LLVM_Types::initSGTypes1() {
        std::vector<llvm::Type*> fields=make_obj_header();
        
        // -- object header --
        std::vector<llvm::Type*> obj_header(make_obj_header());
        
        // -- sg_value adaptation --
        // type to represent objects
        std::vector<llvm::Type*> obj_fields(obj_header);
        val_header_off = obj_fields.size();
        // structure
        val = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        pval = llvm::PointerType::get(val, 0);
        ppval = llvm::PointerType::get(pval, 0);
        
        // -- sg_fun_val adaptation --
        funval = getFunVal_IRDef();
        pfunval = llvm::PointerType::get(funval,0);
        
    }
    
    std::vector<llvm::Type*> LLVM_Types::make_obj_header() {
        std::vector<llvm::Type*> obj_fields;
        // type, pointer
        obj_fields.push_back(pvoid);
        // refcount
        obj_fields.push_back(i64_tp);
        // hc
        obj_fields.push_back(pvoid);
        // fields
        obj_fields.push_back(pvoid);
        return obj_fields;
    }
    
    llvm::StructType* LLVM_Types::getFunVal_IRDef() {
        std::vector<llvm::Type*> fields=make_obj_header();
        
        // nargs
        fields.push_back(i64_tp);
        
        // vd
        fields.push_back(i64_tp);
        
        // symbol
        fields.push_back(symcode_tp);
        
        // f
        fields.push_back(pvoid);
        
        return llvm::StructType::get(llvm::getGlobalContext(), fields);
    }
    
    llvm::StructType* LLVM_Types::getListVal_IRDef() {
        std::vector<llvm::Type*> fields=make_obj_header();
        
        // n
        fields.push_back(i64_tp);
        
        // cap
        fields.push_back(i64_tp);
        
        // objects
        fields.push_back(ppval);
        
        return llvm::StructType::get(llvm::getGlobalContext(), fields);
    }
    
    LLVM_Types* getLLVMTypes() {
        return getLLVMUtil()->types();
    }
    
    // -- LLVM_Util --
    
    LLVM_Util::LLVM_Util() {
        mod = new llvm::Module("llvm_utils", llvm::getGlobalContext());
        llvmt = new LLVM_Types();
        initFunctions();
    }
    
    void LLVM_Util::initFunctions() {
        std::vector<llvm::Type*> args;
        
        // f_check_fvaltype
        args.clear();
        args.push_back(types()->pfunval);
        f_check_fvaltype_tp = llvm::FunctionType::get(types()->void_tp, args, false);
        f_check_fvaltype = (llvm::Function*)mod->getOrInsertFunction("__f_check_fvaltype", f_check_fvaltype_tp);
    }
    
    void LLVM_Util::mapFunctions(llvm::ExecutionEngine* e) {
        e->addGlobalMapping(f_check_fvaltype, (void*)(sg_check_fun_val));
    }
    
    void LLVM_Util::storeFP(sg_fun_val* f, void* addr) {
        AN(sg_check_fun_val(f));
        llvm::ExecutionEngine* e = llvmi->getee();
        
        void *_f_store_fp_addr = e->getPointerToFunction(f_store_fp);
        
        void (*f_store_fp_call)(void*) = (void (*)(void*))_f_store_fp_addr;
        f_store_fp_call(addr);
    }
    
    void LLVM_Util::checkFunValTp(llvm::Value* fv, llvm::IRBuilder<>* builder) {
        std::vector<llvm::Value*> args;
        AT(fv->getType() == types()->pfunval, "Expected pointer to funval");
        
        args.push_back(fv);
        builder->CreateCall(f_check_fvaltype, args);
    }
    
    LLVM_Util* getLLVMUtil() {
        sg_session* ses=sg_global_session();
        if(!ses->llvmu)
            ses->llvmu = (void*)(new LLVM_Util());
        LLVM_Util* u = (LLVM_Util*)ses->llvmu;
        return u;
    }
    
}
